#include "Graphics/sdlgpu.h"
#include "Graphics/renderer.h"
#include "SDL3_image/SDL_image.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"

using namespace Jin;

struct Vertex {
	glm::vec3 pos;
	glm::vec2 uv;
};

struct MVP {
	glm::mat4 model;
	glm::mat4 view;
	glm::mat4 proj;
};

struct AppContext {

	std::unique_ptr<SDLWindow> window;
	std::unique_ptr<GPUDevice> gpuDevice;
	std::unique_ptr<GPUBuffer> vertexBuffer;
	std::unique_ptr<GPUTexture> texture;
	std::unique_ptr<GPUTexture> depthTexture;

	MVP mvp{};
	float rotateY = 0.0f;
	float rotateX = 0.0f;

	AppContext() {
		static SDLFramework sdlfx;
		printf("SDL version: %d.%d.%d\n", sdlfx.version.major, sdlfx.version.minor, sdlfx.version.micor);
	}

	bool InitGPUDevice() {
		gpuDevice = std::make_unique<GPUDevice>(*window);
		if (!*gpuDevice) {
			printf("Create GPU Device failed!\n");
			return false;
		}
		printf("GPU Device Driver: %s\n", gpuDevice->GetDeviceDriver());

		gpuDevice->SetSwapchainParameters(SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_IMMEDIATE);

		SDLFileStorage storage("shaders");
		if (!storage) {
			printf("open shaders storage failed! %s\n", SDL_GetError());
			return false;
		}
		std::vector<Uint8> vertShaderCode;
		if (!storage.ReadFile("shader4.vert.spv", vertShaderCode)) {
			printf("read shader3.vert.spv failed! %s\n", SDL_GetError());
			return false;
		}
		std::vector<Uint8> fragShaderCode;
		if (!storage.ReadFile("shader4.frag.spv", fragShaderCode)) {
			printf("read shader3.frag.spv failed! %s\n", SDL_GetError());
			return false;
		}
		gpuDevice->onSetVertexShaderCreateInfo = [&vertShaderCode](GPUShaderCreateInfo& vertexShaderInfo, stringv driver) {
			vertexShaderInfo.code = vertShaderCode.data();
			vertexShaderInfo.code_size = vertShaderCode.size();
			vertexShaderInfo.num_uniform_buffers = 1;
			};
		gpuDevice->onSetFragmentShaderCreateInfo = [&fragShaderCode](GPUShaderCreateInfo& fragmentShaderInfo, stringv driver) {
			fragmentShaderInfo.code = fragShaderCode.data();
			fragmentShaderInfo.code_size = fragShaderCode.size();
			fragmentShaderInfo.num_samplers = 1;
			};
		if (!gpuDevice->CreateShaders()) {
			return false;
		}
		GPUVertexBufferDescription vertexBufferDesc[1]{};
		vertexBufferDesc[0].slot = 0;
		vertexBufferDesc[0].input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
		vertexBufferDesc[0].instance_step_rate = 0;
		vertexBufferDesc[0].pitch = sizeof(Vertex);
		SDL_GPUVertexAttribute vertexAttributes[2]{};
		vertexAttributes[0].location = 0;
		vertexAttributes[0].buffer_slot = 0;
		vertexAttributes[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
		vertexAttributes[0].offset = offsetof(Vertex, pos);
		vertexAttributes[1].location = 1;
		vertexAttributes[1].buffer_slot = 0;
		vertexAttributes[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
		vertexAttributes[1].offset = offsetof(Vertex, uv);
		gpuDevice->onSetGraphicsPipelineCreateInfo = [&vertexBufferDesc, &vertexAttributes](GPUGraphicsPipelineCreateInfo& pipelineInfo) {
			pipelineInfo.vertex_input_state.vertex_attributes = vertexAttributes;
			pipelineInfo.vertex_input_state.num_vertex_attributes = std::size(vertexAttributes);
			pipelineInfo.vertex_input_state.vertex_buffer_descriptions = vertexBufferDesc;
			pipelineInfo.vertex_input_state.num_vertex_buffers = std::size(vertexBufferDesc);
			pipelineInfo.target_info.has_depth_stencil_target = true;
			pipelineInfo.target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
			pipelineInfo.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS;
			pipelineInfo.depth_stencil_state.back_stencil_state.fail_op = SDL_GPU_STENCILOP_ZERO;
			pipelineInfo.depth_stencil_state.back_stencil_state.pass_op = SDL_GPU_STENCILOP_ZERO;
			pipelineInfo.depth_stencil_state.back_stencil_state.depth_fail_op = SDL_GPU_STENCILOP_ZERO;
			pipelineInfo.depth_stencil_state.back_stencil_state.compare_op = SDL_GPU_COMPAREOP_NEVER;
			pipelineInfo.depth_stencil_state.compare_mask = 0xff;
			pipelineInfo.depth_stencil_state.write_mask = 0xff;
			pipelineInfo.depth_stencil_state.enable_depth_test = true;
			pipelineInfo.depth_stencil_state.enable_depth_write = true;
			pipelineInfo.depth_stencil_state.enable_stencil_test = false;
			};
		if (!gpuDevice->CreateGraphicsPipeline()) {
			return false;
		}
		if (!gpuDevice->CreateSampler()) {
			return false;
		}
		return true;
	}

	void UploadVertexData() {
		Vertex vertices[]{
			Vertex{ { -0.5f, -0.5f, -0.5f }, { 0.0f, 0.0f} },
			Vertex{ {  0.5f, -0.5f, -0.5f }, { 1.0f, 0.0f} },
			Vertex{ {  0.5f,  0.5f, -0.5f }, { 1.0f, 1.0f} },
			Vertex{ {  0.5f,  0.5f, -0.5f }, { 1.0f, 1.0f} },
			Vertex{ { -0.5f,  0.5f, -0.5f }, { 0.0f, 1.0f} },
			Vertex{ { -0.5f, -0.5f, -0.5f }, { 0.0f, 0.0f} },
				  					  
			Vertex{ { -0.5f, -0.5f,  0.5f }, { 0.0f, 0.0f} },
			Vertex{ {  0.5f, -0.5f,  0.5f }, { 1.0f, 0.0f} },
			Vertex{ {  0.5f,  0.5f,  0.5f }, { 1.0f, 1.0f} },
			Vertex{ {  0.5f,  0.5f,  0.5f }, { 1.0f, 1.0f} },
			Vertex{ { -0.5f,  0.5f,  0.5f }, { 0.0f, 1.0f} },
			Vertex{ { -0.5f, -0.5f,  0.5f }, { 0.0f, 0.0f} },
				  					  
			Vertex{ { -0.5f,  0.5f,  0.5f }, { 1.0f, 0.0f} },
			Vertex{ { -0.5f,  0.5f, -0.5f }, { 1.0f, 1.0f} },
			Vertex{ { -0.5f, -0.5f, -0.5f }, { 0.0f, 1.0f} },
			Vertex{ { -0.5f, -0.5f, -0.5f }, { 0.0f, 1.0f} },
			Vertex{ { -0.5f, -0.5f,  0.5f }, { 0.0f, 0.0f} },
			Vertex{ { -0.5f,  0.5f,  0.5f }, { 1.0f, 0.0f} },
				  					  
			Vertex{ {  0.5f,  0.5f,  0.5f }, { 1.0f, 0.0f} },
			Vertex{ {  0.5f,  0.5f, -0.5f }, { 1.0f, 1.0f} },
			Vertex{ {  0.5f, -0.5f, -0.5f }, { 0.0f, 1.0f} },
			Vertex{ {  0.5f, -0.5f, -0.5f }, { 0.0f, 1.0f} },
			Vertex{ {  0.5f, -0.5f,  0.5f }, { 0.0f, 0.0f} },
			Vertex{ {  0.5f,  0.5f,  0.5f }, { 1.0f, 0.0f} },
				  					  
			Vertex{ { -0.5f, -0.5f, -0.5f }, { 0.0f, 1.0f} },
			Vertex{ {  0.5f, -0.5f, -0.5f }, { 1.0f, 1.0f} },
			Vertex{ {  0.5f, -0.5f,  0.5f }, { 1.0f, 0.0f} },
			Vertex{ {  0.5f, -0.5f,  0.5f }, { 1.0f, 0.0f} },
			Vertex{ { -0.5f, -0.5f,  0.5f }, { 0.0f, 0.0f} },
			Vertex{ { -0.5f, -0.5f, -0.5f }, { 0.0f, 1.0f} },
				  					  
			Vertex{ { -0.5f,  0.5f, -0.5f }, { 0.0f, 1.0f} },
			Vertex{ {  0.5f,  0.5f, -0.5f }, { 1.0f, 1.0f} },
			Vertex{ {  0.5f,  0.5f,  0.5f }, { 1.0f, 0.0f} },
			Vertex{ {  0.5f,  0.5f,  0.5f }, { 1.0f, 0.0f} },
			Vertex{ { -0.5f,  0.5f,  0.5f }, { 0.0f, 0.0f} },
			Vertex{ { -0.5f,  0.5f, -0.5f }, { 0.0f, 1.0f} }
		};

		GPUTransferBuffer transferBuffer{ *gpuDevice, SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, sizeof(vertices) };
		if (!transferBuffer) {
			printf("Create transfer buffer failed! %s\n", SDL_GetError());
			return;
		}
		memcpy(GPUTransferBufferMapper{ transferBuffer }, vertices, sizeof(vertices));

		vertexBuffer = std::make_unique<GPUBuffer>(*gpuDevice, SDL_GPU_BUFFERUSAGE_VERTEX, (Uint32)sizeof(vertices));
		if (!*vertexBuffer) {
			printf("Create vertex buffer failed! %s\n", SDL_GetError());
			return;
		}

		GPUCommandBuffer cmd{ *gpuDevice };
		if (!cmd) {
			return;
		}
		GPUCopyPass{ cmd }.UploadToGPUBuffer({ transferBuffer, 0 }, { *vertexBuffer, 0, sizeof(vertices) });
	}

	void UploadImageTexture() {
		SDLSurface img{ IMG_Load("images/girl.png") };
		if (!img) {
			printf("load image failed! %s\n", SDL_GetError());
			return;
		}
		if (!img.ConvertToFormat(SDL_PIXELFORMAT_RGBA32)) {
			printf("convert image format failed! %s\n", SDL_GetError());
			return;
		}

		GPUTransferBuffer transferBuffer{ *gpuDevice, SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, (Uint32)(img.Height() * img.Pitch()) };
		if (!transferBuffer) {
			printf("Create transfer buffer failed! %s\n", SDL_GetError());
			return;
		}
		memcpy(GPUTransferBufferMapper{ transferBuffer }, img.GetPixels(), (size_t)(img.Height() * img.Pitch()));

		GPUTextureCreateInfo texCreateInfo{};
		texCreateInfo.type = SDL_GPU_TEXTURETYPE_2D;
		texCreateInfo.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
		texCreateInfo.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
		texCreateInfo.width = (Uint32)img.Width();
		texCreateInfo.height = (Uint32)img.Height();
		texCreateInfo.layer_count_or_depth = 1;
		texCreateInfo.num_levels = 1;
		texCreateInfo.sample_count = SDL_GPU_SAMPLECOUNT_1;
		texture = std::make_unique<GPUTexture>(*gpuDevice, texCreateInfo);
		if (!*texture) {
			printf("Create texture failed! %s\n", SDL_GetError());
			return;
		}

		GPUCommandBuffer cmd{ *gpuDevice };
		if (!cmd) {
			return;
		}
		GPUTextureTransferInfo srcTexInfo{};
		srcTexInfo.transfer_buffer = transferBuffer;
		srcTexInfo.offset = 0;
		srcTexInfo.pixels_per_row = (Uint32)(img.Width());
		srcTexInfo.rows_per_layer = (Uint32)(img.Height());
		GPUTextureRegion dstTexRegion{};
		dstTexRegion.texture = *texture;
		dstTexRegion.mip_level = 0;
		dstTexRegion.layer = 0;
		dstTexRegion.x = 0;
		dstTexRegion.y = 0;
		dstTexRegion.z = 0;
		dstTexRegion.w = (Uint32)(img.Width());
		dstTexRegion.h = (Uint32)(img.Height());
		dstTexRegion.d = 1;
		GPUCopyPass{ cmd }.UploadToGPUTexture(srcTexInfo, dstTexRegion);
	}

	void CreateDepthTexture(Uint32 width, Uint32 height) {
		GPUTextureCreateInfo texCreateInfo{};
		texCreateInfo.type = SDL_GPU_TEXTURETYPE_2D;
		texCreateInfo.format = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
		texCreateInfo.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
		texCreateInfo.width = width;
		texCreateInfo.height = height;
		texCreateInfo.layer_count_or_depth = 1;
		texCreateInfo.num_levels = 1;
		texCreateInfo.sample_count = SDL_GPU_SAMPLECOUNT_1;
		depthTexture = std::make_unique<GPUTexture>(*gpuDevice, texCreateInfo);
		if (!*depthTexture) {
			printf("Create depth texture failed! %s\n", SDL_GetError());
			return;
		}
	}

	void UpdateMVP() {
		mvp.model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f));
		mvp.model = mvp.model * glm::rotate(glm::mat4(1.0f), glm::radians(rotateY), glm::vec3(0.0f, 1.0f, 0.0f));
		mvp.model = mvp.model * glm::rotate(glm::mat4(1.0f), glm::radians(rotateX), glm::vec3(1.0f, 0.0f, 0.0f));
	}

	void Present() {
		rotateX += 0.1f;
		rotateY += 0.1f;

		if (window->GetWindowFlags() & SDL_WINDOW_MINIMIZED) {
			return;
		}
		GPUCommandBuffer cmd{ *gpuDevice };
		if (!cmd) {
			return;
		}
		GPUSwapchainTexture swapchainTexture{ cmd, *window };
		if (!swapchainTexture) {
			return;
		}
		GPUColorTargetInfo colorTargetInfo{};
		colorTargetInfo.texture = swapchainTexture;
		colorTargetInfo.clear_color = { 0.2f, 0.3f, 0.3f, 1.0f };
		colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
		colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
		colorTargetInfo.mip_level = 0;
		colorTargetInfo.layer_or_depth_plane = 0;
		colorTargetInfo.cycle = true;
		colorTargetInfo.cycle_resolve_texture = false;
		GPUDepthStencilTargetInfo depthStencilTargetInfo{};
		depthStencilTargetInfo.texture = *depthTexture;
		depthStencilTargetInfo.clear_depth = 1.0f;
		depthStencilTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
		depthStencilTargetInfo.store_op = SDL_GPU_STOREOP_DONT_CARE;
		depthStencilTargetInfo.cycle = false;
		GPURenderPass renderPass{ cmd, &colorTargetInfo, 1, &depthStencilTargetInfo };
		if (!renderPass) {
			return;
		}

		renderPass.BindGraphicsPipeline(*gpuDevice->pipeline);
		renderPass.BindVertexBuffers(0, { *vertexBuffer, 0 });
		renderPass.BindTextureSampler(0, { *texture, *gpuDevice->texSampler });

		UpdateMVP();
		cmd.PushVertexUniforms(0, &mvp, sizeof(mvp));

		GPUViewport viewport{};
		viewport.x = 0.0f;
		viewport.y = 0.0f;
		viewport.w = (float)swapchainTexture.width;
		viewport.h = (float)swapchainTexture.height;
		viewport.min_depth = 0.0f;
		viewport.max_depth = 1.0f;
		renderPass.SetViewport(viewport);
		renderPass.DrawPrimitives(36, 1, 0, 0);
	}

	bool Init() {
		auto& sdlfx = SDLFramework::GetInstance();
		sdlfx.SetFPS(200);
		window = std::make_unique<SDLWindow>(_T("testSDLGPU"), 800, 600, 0);
		if (!*window) {
			return false;
		}
		sdlfx.onFPS = [this, &sdlfx](Uint32 fps) {
			window->SetTitle(std::format(_T("fps:{}, delta:{}"), fps, sdlfx.deltaTime));
			};
		if (!InitGPUDevice()) {
			return false;
		}

		UploadVertexData();
		if (!vertexBuffer || !*vertexBuffer) {
			return false;
		}
		UploadImageTexture();
		if (!texture || !*texture) {
			return false;
		}
		CreateDepthTexture(800, 600);
		if (!depthTexture || !*depthTexture) {
			return false;
		}

		mvp.view = glm::mat4(1.0f);
		//mvp.view = glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, 1.0f, 0.0f));
		mvp.proj = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
		UpdateMVP();

		return true;
	}

	void Run() {
		auto& sdlfx = SDLFramework::GetInstance();
		bool runing = true;
		while (runing) {
			sdlfx.CountFrame();
			while (sdlfx.PollEvent()) {
				if (sdlfx.event.type == SDL_EVENT_QUIT) {
					runing = false;
					break;
				}
			}
			Present();
		}
	}

	void Cleanup() {
		gpuDevice->WaitIdle();
	}
};

int main() {
	try {
		AppContext app;
		if (app.Init()) {
			app.Run();
		}
		app.Cleanup();
	}
	catch (const std::exception& e) {
		printf("App error! '%s'\n", e.what());
		return -1;
	}
	return 0;
}